package cn.elwy.rcp.scm.util;

import java.io.File;
import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNDirEntry;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNCommitClient;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNUpdateClient;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import cn.elwy.common.exception.RunException;
import cn.elwy.common.util.AssertUtil;

/**
 * SVNKit 封装工具类
 * 
 * @author huangsq
 * @version 1.0, 2014-06-16
 * @since 1.0, 2014-06-16
 */
public class SvnUtil {

  private static Logger logger = LoggerFactory.getLogger(SvnUtil.class);

  public static final String SVN_BASE_DIRS[] = { "trunk", "branches", "tags" };

  static {
    // 设置通过不同的协议初始化版本库
    FSRepositoryFactory.setup();
    DAVRepositoryFactory.setup();
    SVNRepositoryFactoryImpl.setup();
  }

  /**
   * 获取SVN客户端管理对象
   * 
   * @param url SVN的URL
   * @param username 用户名
   * @param password 密码
   * @return
   * @throws SVNException
   */
  public static SVNClient getClientManager(String url, String username, String password) throws SVNException {
    return getClientManager(getSVNURL(url), username, password);
  }

  /**
   * 获取SVN客户端管理对象
   * 
   * @param svnURL SVN的URL
   * @param username 用户名
   * @param password 密码
   * @return
   * @throws SVNException
   */
  private static SVNClient getClientManager(SVNURL svnURL, String username, String password) throws SVNException {
    SVNRepository repository = SVNRepositoryFactory.create(svnURL);
    // 创建身份验证管理器
    ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username, password);
    // 设置身份验证管理器
    repository.setAuthenticationManager(authManager);
    SVNNodeKind nodeKind = repository.checkPath("", -1);
    if (nodeKind == SVNNodeKind.NONE) {
      throw new RunException("'" + svnURL.getPath() + "' is not exists");
    }
    // 创建默认选项
    DefaultSVNOptions options = SVNWCUtil.createDefaultOptions(true);
    SVNClientManager clientManager = SVNClientManager.newInstance(options, authManager);
    SVNClient svnClient = new SVNClient(username, password, svnURL, repository, clientManager);
    return svnClient;
  }

  /**
   * 转换成SVN的URL对象
   * 
   * @param url svn的URL
   * @return
   * @throws SVNException
   */
  public static SVNURL getSVNURL(String url) throws SVNException {
    return SVNURL.parseURIEncoded(url);
  }

  /**
   * 检出SVN URL的全部内容到本地
   * 
   * @param clientManager SVN客户端管理对象
   * @param url svn地址
   * @param destFile 检出的位置
   * @return
   * @throws SVNException
   */
  public static long checkout(SVNClientManager clientManager, String url, File destFile) throws SVNException {
    return checkout(clientManager, getSVNURL(url), destFile, SVNRevision.HEAD, SVNDepth.INFINITY);
  }

  /**
   * 检出SVN URL的全部内容到本地
   * 
   * @param clientManager SVN客户端管理对象
   * @param url svn地址
   * @param destFile 检出的位置
   * @return
   * @throws SVNException
   */
  public static long checkout(SVNClientManager clientManager, SVNURL url, File destFile) throws SVNException {
    return checkout(clientManager, url, destFile, SVNRevision.HEAD, SVNDepth.INFINITY);
  }

  /**
   * 检出SVN URL的内容到本地
   * 
   * @param clientManager SVN客户端管理对象
   * @param url svn地址
   * @param destFile 检出的位置
   * @param vision 检出的版本
   * @param depth 检出的深度，目录、子目录、文件
   * @return
   * @throws SVNException
   */
  public static long checkout(SVNClientManager clientManager, String url, File destPath, SVNRevision vision,
      SVNDepth depth) throws SVNException {
    boolean ignoreExternals = false;
    return checkout(clientManager, getSVNURL(url), destPath, vision, depth, ignoreExternals);
  }

  /**
   * 检出SVN URL的内容到本地
   * 
   * @param clientManager SVN客户端管理对象
   * @param url svn地址
   * @param destFile 检出的位置
   * @param vision 检出的版本
   * @param depth 检出的深度，目录、子目录、文件
   * @return
   * @throws SVNException
   */
  public static long checkout(SVNClientManager clientManager, SVNURL url, File destPath, SVNRevision vision,
      SVNDepth depth) throws SVNException {
    boolean ignoreExternals = false;
    return checkout(clientManager, url, destPath, vision, depth, ignoreExternals);
  }

  /**
   * 检出SVN URL的内容到本地
   * 
   * @param clientManager SVN客户端管理对象
   * @param url svn地址
   * @param destFile 检出的位置
   * @param HEAD 检出的版本
   * @param depth 检出的深度，目录、子目录、文件
   * @param ignoreExternals sets externals not to be ignored during the checkout
   * @return
   * @throws SVNException
   */
  private static long checkout(SVNClientManager clientManager, SVNURL url, File destPath, SVNRevision revision,
      SVNDepth depth, boolean ignoreExternals) throws SVNException {
    SVNUpdateClient updateClient = clientManager.getUpdateClient();
    // sets externals not to be ignored during the checkout
    updateClient.setIgnoreExternals(ignoreExternals);
    // returns the number of the HEAD at which the working copy is
    return updateClient.doCheckout(url, destPath, revision, revision, depth, ignoreExternals);
  }

  /**
   * 清理本地工作副本
   * 
   * @param clientManager SVN客户端管理对象
   * @param path 本地工作副本
   * @return
   * @throws SVNException
   */
  public static void cleanup(SVNClientManager clientManager, File path) throws SVNException {
    clientManager.getWCClient().doCleanup(path);
  }

  /**
   * 添加到SVN本地版本控制中
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @throws SVNException
   */
  public static void addEntry(SVNClientManager clientManager, File localPath) throws SVNException {
    addEntry(clientManager, new File[] { localPath });
  }

  /**
   * 添加到SVN本地版本控制中
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPaths 一组工作副本的路径
   * @throws SVNException
   */
  private static void addEntry(SVNClientManager clientManager, File[] localPaths) throws SVNException {
    clientManager.getWCClient().doAdd(localPaths, true, false, false, SVNDepth.INFINITY, false, false, true);
  }

  /**
   * 在SVN服务器上创建一个目录
   * 
   * @param clientManager SVN客户端管理对象
   * @param url svn的路径
   * @param message 提交的日志信息
   * @return
   * @throws SVNException
   */
  public static SVNCommitInfo makeDirectory(SVNClientManager clientManager, SVNURL url, String message)
      throws SVNException {
    return makeDirectory(clientManager, new SVNURL[] { url }, message);
  }

  /**
   * 在SVN服务器上创建一个目录
   * 
   * @param clientManager SVN客户端管理对象
   * @param svnURLs 一组svn的路径
   * @param message 提交的日志信息
   * @return
   * @throws SVNException
   */
  public static SVNCommitInfo makeDirectory(SVNClientManager clientManager, SVNURL[] svnURLs, String message)
      throws SVNException {
    return clientManager.getCommitClient().doMkDir(svnURLs, message);
  }

  /**
   * 导入一个文件或目录到SVN服务器
   * 
   * @param clientManager SVN客户端管理对象
   * @param svnURL svn的路径
   * @param localPath 文件或目录
   * @param message 提交的日志信息
   * @param isRecursive 是否递归
   * @return
   * @throws SVNException
   */
  public static SVNCommitInfo importFile(SVNClientManager clientManager, SVNURL svnURL, File localPath, String message,
      boolean isRecursive) throws SVNException {
    SVNCommitClient commitClient = clientManager.getCommitClient();
    return commitClient.doImport(localPath, svnURL, message, null, true, true, SVNDepth.fromRecurse(isRecursive));
  }

  /**
   * 获取一个本地副本的版本号
   * 
   * @param clientManager SVN客户端管理对象
   * @param file 工作副本的路径
   * @return
   * @throws SVNException
   */
  public static long getRevision(SVNClientManager clientManager, File file) throws SVNException {
    return clientManager.getStatusClient().doStatus(file, true).getRevision().getNumber();
  }

  public static long getRevision(SVNRepository repository, String path) throws SVNException {
    // 获取版本库的path目录
    SVNDirEntry entry = repository.getDir(path, -1, false, (Collection<?>) null);
    long revision = entry.getRevision();
    return revision;
    // 获取版本库的path目录下的所有条目。参数－1表示是最新版本。
    // Iterator<?> iterator = entries.iterator();
    // SVNDirEntry entry = (SVNDirEntry) iterator.next();
    // while (iterator.hasNext()) {
    // System.out.println("/" + (path.equals("") ? "" : path + "/")
    // + entry.getName() + " (author: '" + entry.getAuthor()
    // + "'; revision: " + revision + "; date: " + entry.getDate() + ")");
    // /*
    // * 检查此条目是否为目录，如果为目录递归执行
    // */
    // if (entry.getKind() == SVNNodeKind.DIR) {
    // listEntries(repository, (path.equals("")) ? entry.getName()
    // : path + "/" + entry.getName());
    //
    // }
    //
    // }
  }

  /**
   * 获取一个副本的状态信息
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @param remote true to check up the status of the item in the repository, that will tell if the local item is
   *          out-of-date (like '-u' option in the SVN client's 'svn status' command), otherwise false
   * @return
   * @throws SVNException
   */
  public static SVNStatus getStatus(SVNClientManager clientManager, File localPath, boolean remote) throws SVNException {
    return clientManager.getStatusClient().doStatus(localPath, remote);
  }

  /**
   * 提交副本到SVN
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @param message 提交的日志信息
   * @return
   * @throws SVNException
   */
  public static SVNCommitInfo commit(SVNClientManager clientManager, File localPath, String message)
      throws SVNException {
    return commit(clientManager, new File[] { localPath }, message, false);
  }

  /**
   * 提交副本到SVN
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @param message 提交的日志信息
   * @param keepLocks 保持锁
   * @return
   * @throws SVNException
   */
  public static SVNCommitInfo commit(SVNClientManager clientManager, File localPath, String message, boolean keepLocks)
      throws SVNException {
    return commit(clientManager, new File[] { localPath }, message, keepLocks);
  }

  /**
   * 提交副本到SVN
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPaths 一组工作副本的路径
   * @param message 提交的日志信息
   * @param keepLocks 保持锁
   * @return
   * @throws SVNException
   */
  public static SVNCommitInfo commit(SVNClientManager clientManager, File[] localPaths, String message,
      boolean keepLocks) throws SVNException {
    SVNCommitClient commitClient = clientManager.getCommitClient();
    return commitClient.doCommit(localPaths, keepLocks, message, null, null, false, false, SVNDepth.INFINITY);
  }

  /**
   * 更新工作副本到最新的版本
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @return
   * @throws SVNException
   */
  public static long update(SVNClientManager clientManager, File localPath) throws SVNException {
    return update(clientManager, localPath, SVNRevision.HEAD, SVNDepth.INFINITY);
  }

  /**
   * 更新工作副本到指定的版本
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @param HEAD 更新到版本
   * @param depth 更新的深度，目录、子目录、文件
   * @param ignoreExternals 忽略外部
   * @return
   * @throws SVNException
   */
  public static long update(SVNClientManager clientManager, File localPath, SVNRevision revision, SVNDepth depth)
      throws SVNException {
    return update(clientManager, localPath, revision, depth, false);
  }

  /**
   * 更新工作副本到指定的版本
   * 
   * @param clientManager SVN客户端管理对象
   * @param localPath 工作副本的路径
   * @param HEAD 更新到版本
   * @param depth 更新的深度，目录、子目录、文件
   * @param ignoreExternals 忽略外部
   * @return
   * @throws SVNException
   */
  private static long update(SVNClientManager clientManager, File localPath, SVNRevision revision, SVNDepth depth,
      boolean ignoreExternals) throws SVNException {
    SVNUpdateClient updateClient = clientManager.getUpdateClient();
    // sets externals not to be ignored during the update
    updateClient.setIgnoreExternals(ignoreExternals);
    // returns the number of the HEAD wcPath was updated to
    return updateClient.doUpdate(localPath, revision, depth, ignoreExternals, ignoreExternals);
  }

  /**
   * 判断一个文件或目录是否是一个工作副本
   * 
   * @param localPath 本地路径
   * @return
   */
  public static boolean isWorkingCopy(File localPath) {
    if (!localPath.exists()) {
      logger.warn("'" + localPath + "' not exist!");
      return false;
    }
    try {
      if (null == SVNWCUtil.getWorkingCopyRoot(localPath, false)) {
        return false;
      }
    } catch (SVNException e) {
      logger.error(e.getMessage(), e);
    }
    return true;
  }

  /**
   * 验证一个URL在SVN上是否存在
   * 
   * @param url svn的URL
   * @return
   */
  public static boolean isURLExist(String url, String username, String password) {
    try {
      return isURLExist(getSVNURL(url), username, password);
    } catch (SVNException e) {
      logger.warn(e.getMessage(), e);
      return false;
    }
  }

  /**
   * 验证一个URL在SVN上是否存在
   * 
   * @param url svn的URL
   * @return
   */
  public static boolean isURLExist(SVNURL url, String username, String password) {
    try {
      SVNRepository repository = SVNRepositoryFactory.create(url);
      ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username, password);
      repository.setAuthenticationManager(authManager);
      SVNNodeKind nodeKind = repository.checkPath("", -1);
      return nodeKind != SVNNodeKind.NONE;
    } catch (SVNException e) {
      logger.warn(e.getMessage(), e);
    }
    return false;
  }

  /**
   * 获取SVN版本号
   * 
   * @param version 版本字符串
   * @return
   */
  public static SVNRevision isHeadVision(String version) {
    if (AssertUtil.isEmpty(version) || version.equals("HEAD")) {
      return SVNRevision.HEAD;
    }
    try {
      long revisionNumber = Long.parseLong(version);
      return SVNRevision.create(revisionNumber);
    } catch (Exception e) {
      if ("WORKING".equals(version)) {
        return SVNRevision.WORKING;
      } else if ("PREV".equals(version)) {
        return SVNRevision.PREVIOUS;
      } else if ("BASE".equals(version)) {
        return SVNRevision.BASE;
      } else if ("COMMITTED".equals(version)) {
        return SVNRevision.COMMITTED;
      } else {
        return SVNRevision.HEAD;
      }
    }
  }

  /**
   * 获取SVN版本号
   * 
   * @param version 版本字符串
   * @return
   */
  public static SVNRevision getSVNRevision(String version) {
    if (AssertUtil.isEmpty(version) || version.equals("HEAD")) {
      return SVNRevision.HEAD;
    }
    try {
      long revisionNumber = Long.parseLong(version);
      return SVNRevision.create(revisionNumber);
    } catch (Exception e) {
      if ("WORKING".equals(version)) {
        return SVNRevision.WORKING;
      } else if ("PREV".equals(version)) {
        return SVNRevision.PREVIOUS;
      } else if ("BASE".equals(version)) {
        return SVNRevision.BASE;
      } else if ("COMMITTED".equals(version)) {
        return SVNRevision.COMMITTED;
      } else {
        return SVNRevision.HEAD;
      }
    }
  }

  /**
   * 获取仓库根地址
   * 
   * @param repoPath
   * @return
   */
  public static String getProjectRoot(String repoPath) {
    for (int i = 0; i < SVN_BASE_DIRS.length; i++) {
      String base = "/" + SVN_BASE_DIRS[i];
      int pos = repoPath.lastIndexOf(base + "/");
      if (repoPath.endsWith(base))
        return repoPath.substring(0, repoPath.length() - base.length());
      if (pos >= 0)
        return repoPath.substring(0, pos);
    }
    return repoPath;
  }

}